package com.example.library.github.promeg.pinyinhelper.trie;

import com.example.library.github.promeg.pinyinhelper.interval.IntervalTree;
import com.example.library.github.promeg.pinyinhelper.trie.handler.DefaultEmitHandler;
import com.example.library.github.promeg.pinyinhelper.trie.handler.EmitHandler;


import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;

public class Trie {
    private TrieConfig trieConfig;
    private State rootState;

    private Trie(TrieConfig trieConfig) {
        this.trieConfig = trieConfig;
        this.rootState = new State();
    }

    private void addKeyword(String keyword) {
        if (keyword != null && keyword.length() != 0) {
            State currentState = this.rootState;
            char[] var3 = keyword.toCharArray();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Character character = var3[var5];
                if (this.trieConfig.isCaseInsensitive()) {
                    character = Character.toLowerCase(character);
                }

                currentState = currentState.addState(character);
            }

            currentState.addEmit(this.trieConfig.isCaseInsensitive() ? keyword.toLowerCase() : keyword);
        }
    }

    public Collection<Token> tokenize(String text) {
        Collection<Token> tokens = new ArrayList();
        Collection<Emit> collectedEmits = this.parseText(text);
        int lastCollectedPosition = -1;

        Emit emit;
        for(Iterator var5 = collectedEmits.iterator(); var5.hasNext(); lastCollectedPosition = emit.getEnd()) {
            emit = (Emit)var5.next();
            if (emit.getStart() - lastCollectedPosition > 1) {
                tokens.add(this.createFragment(emit, text, lastCollectedPosition));
            }

            tokens.add(this.createMatch(emit, text));
        }

        if (text.length() - lastCollectedPosition > 1) {
            tokens.add(this.createFragment((Emit)null, text, lastCollectedPosition));
        }

        return tokens;
    }

    private Token createFragment(Emit emit, String text, int lastCollectedPosition) {
        return new FragmentToken(text.substring(lastCollectedPosition + 1, emit == null ? text.length() : emit.getStart()));
    }

    private Token createMatch(Emit emit, String text) {
        return new MatchToken(text.substring(emit.getStart(), emit.getEnd() + 1), emit);
    }

    public Collection<Emit> parseText(CharSequence text) {
        DefaultEmitHandler emitHandler = new DefaultEmitHandler();
        this.parseText(text, emitHandler);
        List<Emit> collectedEmits = emitHandler.getEmits();
        if (this.trieConfig.isOnlyWholeWords()) {
            this.removePartialMatches(text, collectedEmits);
        }

        if (this.trieConfig.isOnlyWholeWordsWhiteSpaceSeparated()) {
            this.removePartialMatchesWhiteSpaceSeparated(text, collectedEmits);
        }

        if (!this.trieConfig.isAllowOverlaps()) {
            IntervalTree intervalTree = new IntervalTree(collectedEmits);
            intervalTree.removeOverlaps(collectedEmits);
        }

        return collectedEmits;
    }

    public boolean containsMatch(CharSequence text) {
       Emit firstMatch = this.firstMatch(text);
        return firstMatch != null;
    }

    public void parseText(CharSequence text, EmitHandler emitHandler) {
        State currentState = this.rootState;

        for(int position = 0; position < text.length(); ++position) {
            Character character = text.charAt(position);
            if (this.trieConfig.isCaseInsensitive()) {
                character = Character.toLowerCase(character);
            }

            currentState = this.getState(currentState, character);
            if (this.storeEmits(position, currentState, emitHandler) && this.trieConfig.isStopOnHit()) {
                return;
            }
        }

    }

    public Emit firstMatch(CharSequence text) {
        if (!this.trieConfig.isAllowOverlaps()) {
            Collection<Emit> parseText = this.parseText(text);
            if (parseText != null && !parseText.isEmpty()) {
                return (Emit)parseText.iterator().next();
            }
        } else {
            State currentState = this.rootState;

            for(int position = 0; position < text.length(); ++position) {
                Character character = text.charAt(position);
                if (this.trieConfig.isCaseInsensitive()) {
                    character = Character.toLowerCase(character);
                }

                currentState = this.getState(currentState, character);
                Collection<String> emitStrs = currentState.emit();
                if (emitStrs != null && !emitStrs.isEmpty()) {
                    Iterator var6 = emitStrs.iterator();

                    while(var6.hasNext()) {
                        String emitStr = (String)var6.next();
                        Emit emit = new Emit(position - emitStr.length() + 1, position, emitStr);
                        if (!this.trieConfig.isOnlyWholeWords()) {
                            return emit;
                        }

                        if (!this.isPartialMatch(text, emit)) {
                            return emit;
                        }
                    }
                }
            }
        }

        return null;
    }

    private boolean isPartialMatch(CharSequence searchText, Emit emit) {
        return emit.getStart() != 0 && Character.isAlphabetic(searchText.charAt(emit.getStart() - 1)) || emit.getEnd() + 1 != searchText.length() && Character.isAlphabetic(searchText.charAt(emit.getEnd() + 1));
    }

    private void removePartialMatches(CharSequence searchText, List<Emit> collectedEmits) {
        List<Emit> removeEmits = new ArrayList();
        Iterator var4 = collectedEmits.iterator();

        Emit removeEmit;
        while(var4.hasNext()) {
            removeEmit = (Emit)var4.next();
            if (this.isPartialMatch(searchText, removeEmit)) {
                removeEmits.add(removeEmit);
            }
        }

        var4 = removeEmits.iterator();

        while(var4.hasNext()) {
            removeEmit = (Emit)var4.next();
            collectedEmits.remove(removeEmit);
        }

    }

    private void removePartialMatchesWhiteSpaceSeparated(CharSequence searchText, List<Emit> collectedEmits) {
        long size = (long)searchText.length();
        List<Emit> removeEmits = new ArrayList();
        Iterator var6 = collectedEmits.iterator();

        while(true) {
            Emit emit;
            do {
                if (!var6.hasNext()) {
                    var6 = removeEmits.iterator();

                    while(var6.hasNext()) {
                        emit = (Emit)var6.next();
                        collectedEmits.remove(emit);
                    }

                    return;
                }

                emit = (Emit)var6.next();
            } while((emit.getStart() == 0 || Character.isWhitespace(searchText.charAt(emit.getStart() - 1))) && ((long)(emit.getEnd() + 1) == size || Character.isWhitespace(searchText.charAt(emit.getEnd() + 1))));

            removeEmits.add(emit);
        }
    }

    private State getState(State currentState, Character character) {
        State newCurrentState;
        for(newCurrentState = currentState.nextState(character); newCurrentState == null; newCurrentState = currentState.nextState(character)) {
            currentState = currentState.failure();
        }

        return newCurrentState;
    }

    private void constructFailureStates() {
        Queue<State> queue = new LinkedBlockingDeque();
        Iterator var2 = this.rootState.getStates().iterator();

        while(var2.hasNext()) {
            State depthOneState = (State)var2.next();
            depthOneState.setFailure(this.rootState);
            queue.add(depthOneState);
        }

        while(!queue.isEmpty()) {
            State currentState = (State)queue.remove();
            Iterator var9 = currentState.getTransitions().iterator();

            while(var9.hasNext()) {
                Character transition = (Character)var9.next();
                State targetState = currentState.nextState(transition);
                queue.add(targetState);

                State traceFailureState;
                for(traceFailureState = currentState.failure(); traceFailureState.nextState(transition) == null; traceFailureState = traceFailureState.failure()) {
                }

               State newFailureState = traceFailureState.nextState(transition);
                targetState.setFailure(newFailureState);
                targetState.addEmit(newFailureState.emit());
            }
        }

    }

    private boolean storeEmits(int position, State currentState, EmitHandler emitHandler) {
        boolean emitted = false;
        Collection<String> emits = currentState.emit();
        if (emits != null && !emits.isEmpty()) {
            for(Iterator var6 = emits.iterator(); var6.hasNext(); emitted = true) {
                String emit = (String)var6.next();
                emitHandler.emit(new Emit(position - emit.length() + 1, position, emit));
            }
        }

        return emitted;
    }

    public static TrieBuilder builder() {
        return new TrieBuilder();
    }

    public static class TrieBuilder {
        private TrieConfig trieConfig;
        private Trie trie;

        private TrieBuilder() {
            this.trieConfig = new TrieConfig();
            this.trie = new Trie(this.trieConfig);
        }

        public TrieBuilder caseInsensitive() {
            this.trieConfig.setCaseInsensitive(true);
            return this;
        }

        public TrieBuilder removeOverlaps() {
            this.trieConfig.setAllowOverlaps(false);
            return this;
        }

        public TrieBuilder onlyWholeWords() {
            this.trieConfig.setOnlyWholeWords(true);
            return this;
        }

        public TrieBuilder onlyWholeWordsWhiteSpaceSeparated() {
            this.trieConfig.setOnlyWholeWordsWhiteSpaceSeparated(true);
            return this;
        }

        public TrieBuilder addKeyword(String keyword) {
            this.trie.addKeyword(keyword);
            return this;
        }

        public TrieBuilder stopOnHit() {
            this.trie.trieConfig.setStopOnHit(true);
            return this;
        }

        public Trie build() {
            this.trie.constructFailureStates();
            return this.trie;
        }
    }
}
